-- Basic types (:: is read as - has type of)'a' :: CharTrue :: Bool"a" :: [Char](True, 'a') :: (Bool, Char)-- Functions-- removeNonUppercase :: [Char] -> [Char] removeNonUppercase :: String -> StringremoveNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]addThree :: Int -> Int -> Int -> IntaddThree x y z = x + y + z-- Type variableshead :: [a] -> a
Types
Int
Integer - unbounded integer, can be very large
Float
Double
Bool
Char
Type variables
Generic types usually represented by lowercase alphabets
Functions that have type variables are called polymorphic functions
Typeclasses & More types
-- Typeclass defn(==) :: (Eq a) => a -> a -> Bool -- Eq or equality typeclass-- Typeclass examples(==) :: (Eq a) => a -> a -> Bool -- Equality, members implement == & /=(>) :: (Ord a) => a -> a -> Bool -- Ordering, members implement >,<,>=,<=show :: (Show a) => a -> String -- Shown as stringsread :: (Read a) => String -> a -- Opposite of Show, reads a string and returns a type which is a member of ReadEnum -- Members are sequentially ordered typesBounded -- Have upper and lower boundNum -- Numeric typeclass, members act like numbers (take in ints and floats)fromIntegral :: (Num b, Integral a) => a -> b -- Num but only integral numbersFloating -- double & float-- Algebraic data types (ADT)data Bool = False | True -- Defining a new typedata Shape = Circle Float | Rectangle Float Float deriving (Show) -- makes part of Show typeclasssurface :: Shape -> Floatsurface (Circle r) = pi * r ^ 2surface (Rectangle x1 y1) = (abs $ x1 - y1) * (abs $ y1 - x1)-- Recordsdata Person = Person { firstName :: String , lastName :: String , age :: Int } deriving (Show)Person {firstName="", lastName="", age=5}-- Type parametersdata Maybe a = Nothing | Just adata (Ord k) => Map k v -- Bad practice-- Deriving typeclassesdata Person = Person { firstName :: String , lastName :: String , age :: Int } deriving (Eq) -- All fields of type must be constrained by Eqdata Day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday deriving (Eq, Ord, Show, Read, Bounded, Enum) -- Enum applied for successor & predecessor and Bounded for max, min bounds-- Type synonymstype String = [Char] -- type alias, not a new typetype AssocList k v = [(k,v)] -- Parameterizedtype IntMap = Map Int -- Partially applied (v)
Sort of interface that defines behaviour. If a type is a part of a typeclass, that means that it supports and implements the behaviour the typeclass describes.
=> is a class constraint. The type of values must be a member of the typeclass
Creating types
We use the data keyword to create types.
data A = B
Here, A is the type name and B is called a value constructor. Both are capital
Value constructors can have fields which can contain values
These fields are actually params to the value constructors which are functions
We can pattern match value constructors but not use them as types.
It's common to use the same name as the type if there's only one value constructor
Records
Creates functions that can lookup fields in the type
Order does not matter
Type parameters
Type constructors take types as parameters to produce new types. (Generics)
Type constructors can contain class constraints but this is not good practice as you will have to include the constraint in functions anyways.
Derived types
Ex: When we derive the Eq instance for a type and then try to compare two values of that type with == or /=, Haskell will see if the value constructors match and then check data inside too
Pattern matching
-- Fn body pattern matchinglucky :: (Integral a) => a -> Stringlucky 7 = "LUCKY NUMBER SEVEN!"lucky x = "Sorry, you're out of luck, pal!"factorial :: (Integral a) => a -> afactorial 0 = 1factorial n = n * factorial (n - 1)addVectors :: (Num a) => (a, a) -> (a, a) -> (a, a) -- Tuple patternsaddVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)head' :: [a] -> a -- List patterns using conshead' [] = error "Can't call head on an empty list, dummy!"head' (x:_) = xlength' :: (Num b) => [a] -> blength' [] = 0length' (_:xs) = 1 + length' xscapital :: String -> Stringcapital "" = "Empty string, whoops!"capital all@(x:xs) = all ++ " " ++ xs ++ " " ++ [x] -- for hello - hello ello h-- GuardsbmiTell :: (RealFloat a) => a -> StringbmiTell bmi | bmi <= 18.5 = "You're underweight" | bmi <= 25.0 = "You're normal." | bmi <= 30.0 = "You're fat" | otherwise = "You're whale"-- where & let-in bindingsfoo :: Int -> Intfoo bar = a + b where a = bar * 5 b = bar + 2foo :: Int -> Intfoo bar = let a = bar * 5 b = bar + 2 in a + b[let square x = x * x; y = 5 in (square 5, square 3, square y)]calcBmis :: (RealFloat a) => [(a, a)] -> [a]-- in not required due to predefined name visibilitycalcBmis xs = [bmi | (w, h) <- xs, let bmi = w / h ^ 2, bmi >= 25.0]-- case expressionshead' :: [a] -> ahead' xs = case xs of [] -> error "No head for empty lists!" (x:_) -> x
Pattern matching consists of specifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns
Haskell does not check for exhaustivity of patterns and hence, matches can fail
_ is used to ignore a pattern
Patterns (@) are a handy way of breaking something up according to a pattern and binding it to names whilst still keeping a reference to the whole thing.
Guards are boolean exprs and are indicated by pipes that follow a function's name and params
Bindings
Names defined in where are only visible to that function
Can use pattern matching in where name declarations
let-in is more constrained than where but lets you bind variables anywhere unlike where.
let-in is an expression but where is a syntactic construct
Function pattern matching is syntactic sugar for case exprs
Recursion
maximum' :: (Ord a) => [a] -> amaximum' [] = error "maximum of empty list"maximum' [x] = x -- maximum' (x:xs) -- | x > maxTail = x -- | otherwise = maxTail -- where maxTail = maximum' xsmaximum' (x:xs) = max x (maximum' xs)
Recursion is a way of defining functions in which the function is applied inside its own definition. Due to the lack of loops in Haskell, we use recursion.
Define a terminating edge/base case and work your way through to find the general case.
Higher order functions
-- Curryingmax 4 5(max 4) 5compareWithHundred x = compare 100 xcompareWithHundred = compare 100divideByTen = (/10) -- Infix curry (does x / 10)-- Higher order fnsapplyTwice :: (a -> a) -> a -> aapplyTwice f x = f (f x)zipWith' :: (a -> b -> c) -> [a] -> [b] -> [c]zipWith' _ [] _ = []zipWith' _ _ [] = []zipWith' f (x:xs) (y:ys) = f x y : zipWith' f xs ysmap :: (a -> b) -> [a] -> [b]map _ [] = []map f (x:xs) = f x : map f xsfilter :: (a -> Bool) -> [a] -> [a]filter _ [] = []filter p (x:xs) | p x = x : filter p xs | otherwise = filter p xstakeWhile (<10000) [1..] -- Takes nums while predicate is true-- LambdaszipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5]map (\(a,b) -> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)] -- With pattern matching-- Foldssum' :: (Num a) => [a] -> a-- sum' xs = foldl (\acc x -> acc + x) 0 xssum' = foldl (+) 0elem' :: (Eq a) => a -> [a] -> Boolelem' y ys = foldl (\acc x -> if x == y then True else acc) False ysmap' :: (a -> b) -> [a] -> [b]map' f xs = foldr (\x acc -> f x : acc) [] xs -- Left fold requires ++ which is costly-- Function application($) :: (a -> b) -> a -> bf $ x = f xmap ($ 3) [(4+), (10*), (^2), sqrt]-- Function composition(.) :: (b -> c) -> (a -> b) -> a -> cf . g = \x -> f (g x)map (negate . abs) [5,-3,-6,7,-3,2,-19,24]map (negate . sum . tail) [[1..5],[3..6],[1..7]](sum . replicate 5 . max 6.7) 8.9 -- Multiple param compositionfn = ceiling . negate . tan . cos . max 50 -- Point free style
Functions that can take functions as parameters and return functions as return values are called higher order functions.
Every fn in Haskell takes only one parameter. The functions that accept more are curried.
max :: (Ord a) => a -> a -> a can be read as max :: (Ord a) => a -> (a -> a) which explains why type declarations are separated by arrows.
Infix functions can be partially applied by using sections - surrounding it with parentheses
(-5) is an exception is just negative 5
Function types need a mandatory parentheses because -> is naturally right associative
Lambdas are anonymous functions usually to pass them to a higher order function. They are expressions. Lambdas are normally surrounded by parentheses unless we mean for them to extend all the way to the right.
A fold takes a binary function, a starting value (the accumulator) and a list to fold up. The resultant acc is returned. foldl starts from the left and foldr from the right.
scanl & scanr are similar but report intermediate values in a list instead of the acc.
Function application or $ has the lowest precedence and right associative.
Modules
-- Importsimport Data.ListnumUniques :: (Eq a) => [a] -> IntnumUniques = length . nub -- nub exists in Data.List and gets unique elementsimport Data.List (nub, sort)import Data.List hiding (nub)import qualified Data.Map as M -- Qualified imports (M.foo)-- Data.Listintersperse '.' "MONKEY" -- "M.O.N.K.E.Y"intercalate [0,0,0] [[1,2,3],[4,5,6],[7,8,9]] -- [1,2,3,0,0,0,4,5,6,0,0,0,7,8,9]transpose [[1,2,3],[4,5,6],[7,8,9]]concat [[3,4,5],[2,3,4],[2,1,1]] -- [3,4,5,2,3,4,2,1,1]and $ map (==4) [4,4,4,3,4] -- Falseor $ map (==4) [2,3,4,5,6,1] -- Trueany (==4) [2,3,5,6,1,4]any (==4) [2,3,5,6,1,4]iterate (*2) 1 -- Infinite multiplesgroup [1,1,2,2,2,2,3,3,2,2,2,5,6,7] -- [[1,1],[2,2,2,2],[3,3],[2,2,2],[5],[6],[7]]find (>4) [1,2,3,4,5,6] -- Just 5lines "first line\nsecond line" -- ["first line","second line"][1..10] \\ [2,5,9] -- [1,3,4,6,7,8,10] (List difference)groupBy ((==) `on` (> 0)) values -- Group by equality on condition - greater than zero-- Maps / Association listsphoneBook = [("betty","555-2938") ,("bonnie","452-2928") ,("patsy","493-2928") ]findKey :: (Eq k) => k -> [(k,v)] -> Maybe vfindKey key = foldr (\(k,v) acc -> if key == k then Just v else acc) NothingMap.null $ Map.fromList [(2,3),(5,5)]Map.insert 5 600 . Map.insert 4 200 . Map.insert 3 100 $ Map.empty-- Creating modulesmodule Geometry( Point(..) -- Exported all constructors of a datatype, Shape(Circle), sphereVolume -- Exported functions, sphereArea) where
A Haskell module is a collection of related functions, types and typeclasses.
A Haskell program is a collection of modules where the main module loads up the other modules and then uses the functions defined in them.
The Prelude module is imported by default.
import has to be at the top of the file, before any fn definition
foldl' and foldl1' are stricter versions of their respective lazy versions. When using lazy folds on really big lists, you might often get a stack overflow error. The culprit for that is because the accumulator value isn't actually updated as the folding happens. It makes a promise that it will compute its value when asked to actually produce the result (also called a thunk).
Maps need keys to be orderable as they are internally represented by trees.
All elements of a set are unique and ordered.
Creating modules
The module name is the file name. Ex: Foo
If a module is present in a folder, it's a submodule. Ex: Foo/Bar implies Foo.Bar